<?php
abstract class PaymentBase
{

    /**
     * Construction.
     */
    private function __construct()
    {

    }

    /**
     * 创建订单
     * @param int $userId
     * @param int $paymentDefId  商品配置id
     * @param array $paymentSetting default null
     * @param string $platformToken default null
     * @param array $extra default array()
     * @throws SzException 30503
     * @return ModulePaymentOrderVo $order
     */
    public function createOrder($userId, $paymentDefId, $paymentSetting = null, $platformToken = null, $extra = array())
    {
        if (is_null($paymentSetting)) {
            $paymentSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME_PAY, $paymentDefId, true);

        }
        /**
         * @var SzMySqlModel $orderModel
         * @var ModulePaymentOrderVoList $orderList
         * @var ModulePaymentOrderVo $lastOrder
         */
        $orderModel = SzPersister::get()->getModel(ModulePaymentConst::ORM_NAME_PAYMENT_ORDER);
        $orderList = SzPersister::get()->getVoList(null, ModulePaymentConst::ORM_NAME_PAYMENT_ORDER, $userId);

        $order = new ModulePaymentOrderVo(
            null, // orderId
            $userId, // userId
            $platformToken, // platformToken
            $paymentSetting['productId'], // paymentDefId
            GameService::get()->getSysTime(), // orderTime
            null, // completeTime
            ModulePaymentConst::STATUS_INITIALIZED, // status
            json_encode($extra), // extra
            $paymentSetting['price'], // price
            true
        );

        list($affectedRows, $orderId) = $orderModel->insert($order);
        if ($affectedRows) {
            $order->setOrderId($orderId);
            $order->removeInsertFlag();
            $orderList->setElement($orderId, $order);
            $orderModel->setListOfVoCache(array($order), $orderList->getList());
        } else {
            throw new SzException(30503, 'createOrder', ModulePaymentConst::MODULE_NAME);
        }
        return $order;
    }


    /**
     * Entrance of all complete order & supply *.
     * <pre>
     * NOTE: In this logic process $order object not persisted yet, and not be set into SzPersister yet,
     * it shall be done in later logic.
     * </pre>
     *
     * @param int $userId
     * @param ModulePaymentOrderVo $order
     * @param array $paymentSetting
     * @param int $quantity
     * @throws SzException 30501
     * @return ModulePaymentOrderVo $order
     */
    protected function completeOrderAndSupplyResources($userId, $order, $paymentSetting, $quantity)
    {
        if ($order->getStatus() < ModulePaymentConst::STATUS_PAYED) { // update order status to payed
            $order->setStatus(ModulePaymentConst::STATUS_PAYED);
        }
        switch ($paymentSetting['type']) {
            case GameConst::PAYMENT_TYPE_DIAMOND:
                $order = $this->completeOrderAndSupplyMoney($userId, $order, $paymentSetting, $quantity);
                break;
            case GameConst::PAYMENT_TYPE_COINS:
                $order = $this->completeOrderAndSupplyToken($userId, $order, $paymentSetting, $quantity);
                break;
            case GameConst::PAYMENT_TYPE_PACKAGE:
                $order = $this->completeOrderAndSupplyItems($userId, $order, $paymentSetting, $quantity);
                break;
            default:
                break;
        }
        return $order;
    }

    /**
     * Complete order and supply player token.
     *
     * @param int $userId
     * @param ModulePaymentOrderVo $order
     * @param array $paymentSetting
     * @param int $quantity
     * @throws SzException 30501
     * @return ModulePaymentOrderVo $order
     */
    protected function completeOrderAndSupplyToken($userId, $order, $paymentSetting, $quantity)
    {
        // add items
        if (!$order->getStatus() == ModulePaymentConst::STATUS_PAYED) {
            throw new SzException(30501, array($order->getStatus()), ModulePaymentConst::MODULE_NAME);
        }

        // add commodity value
        $addedCommodityValue = $order->getPrice() * ModulePaymentConst::SINGLE_COMMODITY_VALUE_OF_PAY;

        /** @var ModuleProfileVo $profile */
        $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        ModuleProfile::get()->updateProfileToken($profile, $paymentSetting['count'] * $quantity, true, $addedCommodityValue);
        SzPersister::get()->setVo($profile);

        // change order
        $order->setCompleteTime(SzTime::getTime());
        $order->setStatus(ModulePaymentConst::STATUS_COMPLETED);

        return $order;
    }

    /**
     * Complete order and supply player money.
     *
     * @param int $userId
     * @param ModulePaymentOrderVo $order
     * @param array $paymentSetting
     * @param int $quantity
     * @throws SzException 30501
     * @return ModulePaymentOrderVo $order
     */
    protected function completeOrderAndSupplyMoney($userId, $order, $paymentSetting, $quantity)
    {
        // add items
        if (!$order->getStatus() == ModulePaymentConst::STATUS_PAYED) {
            throw new SzException(30501, array($order->getStatus()), ModulePaymentConst::MODULE_NAME);
        }

        // add commodity value
        $addedCommodityValue = $order->getPrice() * ModulePaymentConst::SINGLE_COMMODITY_VALUE_OF_PAY;

        /** @var ModulePaymentVo $payment */
        $payment = SzPersister::get()->getVo(null, ModulePaymentConst::ORM_NAME_PAYMENT, $userId);
        $this->updateMoneyAmount($payment, $paymentSetting['count'] * $quantity, $addedCommodityValue);
        SzPersister::get()->setVo($payment);

        // change order
        $order->setCompleteTime(SzTime::getTime());
        $order->setStatus(ModulePaymentConst::STATUS_COMPLETED);
        return $order;
    }

    /**
     * Complete order and supply player items.
     *
     * @param int $userId
     * @param ModulePaymentOrderVo $order
     * @param array $paymentSetting
     * @param int $quantity
     * @throws SzException 30501
     * @return ModulePaymentOrderVo $order
     */
    protected function completeOrderAndSupplyItems($userId, $order, $paymentSetting, $quantity)
    {
        // add items
        if (!$order->getStatus() == ModulePaymentConst::STATUS_PAYED) {
            throw new SzException(30501, array($order->getStatus()), ModulePaymentConst::MODULE_NAME);
        }

        $addedCommodityValue = $order->getPrice() * ModulePaymentConst::SINGLE_COMMODITY_VALUE_OF_PAY;
        $index = 0;
        $total = array_sum($paymentSetting['packageWeight']);
        foreach ($paymentSetting['package'] as $entityId => $count) {
            if (isset($paymentSetting['packageWeight'][$index])) {
                $commodityValue = floor($addedCommodityValue * $paymentSetting['packageWeight'][$index] / $total);
            } else {
                $commodityValue = 0;
            }
            $count *= $quantity;
            GameService::get()->handleEntity($userId, $entityId, $count, true, true, array(), true, $commodityValue);
        }

        // change order
        $order->setCompleteTime(SzTime::getTime());
        $order->setStatus(ModulePaymentConst::STATUS_COMPLETED);
        return $order;
    }

    /**
     * Update player money.
     *
     * @param ModulePaymentVo $payment
     * @param int $money possible to be negative number, means minus money
     * @param int $commodityValue default 0，it shall always be positive number
     * @throws SzException 30500
     * @return ModulePaymentVo $payment
     */
    public function updateMoneyAmount($payment, $money, $commodityValue = 0)
    {
        if ($commodityValue > 0 && $money > 0) {
            // 增加money，并且价值大于0，默认为增加付费货币
            $isFreeMoney = false;
        } else {
            // 增加money，并且价值小于等于0，默认为增加免费货币
            // 扣除money，默认为扣除免费货币，免费货币不足，扣除付费货币，并计算扣除的价值
            $isFreeMoney = true;
            $commodityValue = 0;
        }

        $currentMoney = $payment->getMoney() + $money;

        // validate money enough or not
        if ($money < 0 && $currentMoney < 0) {
            throw new SzException(30500, [$currentMoney, $money], ModulePaymentConst::MODULE_NAME); // not enough money
        }

        if ($isFreeMoney || $money < 0) {
            if ($money < 0 && $payment->getBonusMoney() < abs($money)) {
                // means bonus token not enough to pay the amount, also need to minus purchased token
                $changedMoney = abs($payment->getBonusMoney() + $money);
                $changedCommodityValue = -$changedMoney * ($payment->getCommodityValue() / $payment->getPurchasedMoney());
                $payment->setBonusMoney(0);
                $payment->updatePurchasedMoney(-$changedMoney);
                $payment->updateCommodityValue($changedCommodityValue);
            } else {
                $payment->updateBonusMoney($money);
            }
        } else {
            // add token
            if ($isFreeMoney) {
                $payment->updateBonusMoney($money);
            } else {
                $payment->updatePurchasedMoney($money);
                $payment->updateCommodityValue($commodityValue);
            }
        }
        return $payment;
    }

    /**
     * 支付完成接口
     * @param array $extra
     * @return mixed
     */
    abstract public function completeOrder($extra);

    /**
     * 格式化不同平台支付完成后的返回数据
     * @param array $result
     * @return mixed
     */
    abstract public function completeOrderReturnValue($result);
}